import json
import os
import re
from copy import deepcopy
from datetime import datetime

from util.convert import simulation_tool_use
from util.query_openai_api import LLMClient


def get_api_desc_from_apis(apis: list):
    return [api["description"] for api in apis]


def messages_to_function_call(llm_client: LLMClient, questions: list, apis: list, llm_client_2: LLMClient = None, retry: bool = False):
    llm_client_2 = llm_client_2 or llm_client

    current_date = datetime.now().strftime("%Y-%m-%d")
    # apis_desc = '\n'.join([f"第{i + 1}个工具，工具名：{api['name']}, 工具功能：{api['description']}，工具参数：{','.join([k+':'+v['description'] for k,v in api['parameters']['properties'].items()])}" for i, api in enumerate(apis)])
    # apis_desc = '\n'.join([f"第{i + 1}个工具，工具名：{api['name']}, 工具功能：{api['description']}" for i, api in enumerate(apis)])
    apis_desc = '\n'.join([f"第{i + 1}个工具: {json.dumps(api,ensure_ascii=False)}" for i, api in enumerate(apis)])

    user_task_requery_messages = [{"role": "system", "content": f"""你是一个工具选择的人工智能体，你的任务是根据用户的当前查询，完整且独立地改写用户的查询请求，并告诉用户如何使用工具来回答改写后的查询。每个用户查询需要被分解为一系列的子任务，每个子任务将由不同的工具解决。

### 系统信息

- 当前日期：{current_date}
- 可用工具列表：
{apis_desc}

### 思考步骤与规则说明

在进行问题改写和工具选择前，需明确以下问题：

1. **当前查询是否是一个新的、独立的问题（不依赖历史上下文）？**

   - 如果当前查询与前面的问题存在相关性（例如在之前查询的基础上添加了新的查询条件），则不是一个新查询。
   - 如果当前查询与之前查询无关或完全独立，则视为新查询。

2. **完成该查询是否需要使用多个工具？**

请先明确以上两个问题，然后再进行问题的改写和子任务分解。

### 输出格式

请按照如下格式以JSON返回：

```json
[
    {{
        "问题改写": "<根据用户当前查询完整且独立改写后的第一个子任务>",
        "使用的工具编号": <对应的工具编号>
    }},
    {{
        "问题改写": "<根据用户当前查询完整且独立改写后的第二个子任务>",
        "使用的工具编号": <对应的工具编号>
    }}
]
```

### 注意事项与特殊情况说明：

1. 问题改写应当清晰、简洁，完整表达用户的实际需求，去掉无关信息。
2. 改写后的问题应尽可能独立于之前的问题，避免依赖于历史上下文。
3. 问题改写时要尽可能保留原始查询中的关键词，确保每个改写后的问题可以独立明确地表达用户需求。

- **情况1：只需使用一种工具即可解决问题**

此时仅返回一个子任务即可。例如：

用户查询：“今天北京天气如何？”

```json
[
    {{
        "问题改写": "查询今天北京的天气情况",
        "使用的工具编号": 1
    }}
]
```

- **情况2：一种工具需被多次使用以完成多个不同子任务**

每次使用该工具都应作为独立子任务列出。例如：

用户查询：“请分别查询北京和上海今天的天气”

```json
[
    {{
        "问题改写": "查询北京今天的天气情况",
        "使用的工具编号": 1
    }},
    {{
        "问题改写": "查询上海今天的天气情况",
        "使用的工具编号": 1
    }}
]
```

- **情况3：无任何合适工具可用**

此时返回空列表即可。例如：

用户查询：“请告诉我如何烹饪红烧肉”（假设你没有提供烹饪相关的工具）

```json
[]
```

以上情况应根据实际的工具列表和用户查询灵活判断并选择处理方式。
"""}]

    targets=[]
    for i, q in enumerate(questions):
        # Call model with functions defined – along with your system and user messages.

        print("用户输入：",q)
        tmp_msg=deepcopy(user_task_requery_messages)
        user_task_requery_messages.append({"role": "user", "content": q})
        # tmp_msg.append({"role": "user", "content": f"用户输入“{q}”，应该使用哪些工具"})
        tmp_msg.append({"role": "user", "content": f"用户输入“{q}”，一步一步思考，最后返回json"})

        response =llm_client.create_chat_completion(
            tmp_msg,skip_cache=retry,convert=True)

        # print(json.dumps(response, ensure_ascii=False, indent=4))
        # print(response["content"])

        user_task_requery_messages.append({"role": "assistant", "content": re.findall(r"(```json.*?```)", response["content"], re.DOTALL)[0]})

        tasks=json.loads(re.findall(r"```json(.*?)```", response["content"], re.DOTALL)[-1])


        if len(tasks)>0:
            sub_task_return=""
            this_sub_task_targets=[]
            for task in tasks:
                try:
                    api = apis[task["使用的工具编号"]-1]
                except:
                    print("工具编号错误:",task["使用的工具编号"])
                    continue
                api_name = api["name"]
                api_desc = api["description"]
                # print("api",api)

                # 实体识别
                system_prompt =f"""解析输入文本，从中识别并提取特定的参数值，确保提取的准确性和完整性。

current_date: {current_date}

## 指导原则：

- 用户输入包括要提取的文本，以及函数名、函数描述、参数名、参数描述、参数类型。
- 每次仅提取**一个**参数的值。
- 根据函数的参数定义，从输入文本中提取或推理出对应的参数值。
- 如果**参数描述**有格式要求，需要将文本中符合要求的信息转换为符合格式的值。
- 参数类型包括：
  - `string`：字符串，如名称、描述等。
  - `array`：数组，通常为字符串数组。
  - `dict`：字典。
  - `tuple`：元组。
  - `float`：浮点数。
  - `boolean`：布尔值（true/false）。
  - `number`：数值，包括整数和浮点数。
  - `integer`：整数。
- 仅以结构化格式（如 JSON 对象）返回**参数值**。
- 如果未找到符合要求的参数，不要虚构参数，返回 `null`。
- 在输出 JSON 里，添加“推理过程”字段，并放在“参数值”之前，描述提取该参数值的逻辑过程。推理过程包括以下四步：
  1. 首先，我们看到需要提取的参数有关的信息。
  2. 然后，我们能推理出值。
  3. 接着，转为符合要求的格式和类型。
  4. 最后，得到最终的值。

## 示例：

### 示例 1：基本参数提取
**用户历史输入：**
公司将在2024年3月15日于上海举办年度大会，预计有150人参会。
**用户查询：**
创建一个活动
**用户输入：**
```json
{{
  "函数名": "create_event",
  "函数描述": "创建一个活动，包括名称、日期、地点和参与者人数。",
  "参数名": "attendee_count",
  "参数描述": "活动的参与者人数",
  "参数类型": "number"
}}
````

**输出 JSON：**

```json
{{
  "推理过程": "1. 需要提取的参数是参与者人数，用户输入中提到‘预计有150人参会’。2. 这个数字表示活动的参与者人数，因此提取150。3. 参数类型为number，无需转换。4. 最终值为150。",
  "参数值": 150
}}
```

### 示例 2：格式化参数值（日期格式要求为YYYY-MM-DD）

**用户历史输入：**
公司将在2024年3月15日举办年度大会。
**用户查询：**
创建一个活动
**用户输入：**

```json
{{
  "函数名": "create_event",
  "函数描述": "创建一个活动，包括名称、日期、地点和参与者人数。",
  "参数名": "event_date",
  "参数描述": "活动的日期（格式：YYYY-MM-DD）",
  "参数类型": "string"
}}
```

**输出 JSON：**

```json
{{
  "推理过程": "1. 需要提取的参数是活动日期，用户输入中提到‘2024年3月15日’。2. 该日期符合活动日期的描述，因此提取。3. 转换为YYYY-MM-DD格式。4. 最终值为‘2024-03-15’。",
  "参数值": "2024-03-15"
}}
```

### 示例 3：布尔值提取

**用户历史输入：**
本次活动将在2024年3月15日通过Zoom在线举行。
**用户查询：**
创建一个活动
**用户输入：**

```json
{{
  "函数名": "create_event",
  "函数描述": "创建一个活动，包括名称、日期、地点和是否为线上活动。",
  "参数名": "is_online",
  "参数描述": "活动是否为线上活动（true/false）",
  "参数类型": "boolean"
}}
```

**输出 JSON：**

```json
{{
  "推理过程": "1. 需要提取的参数是活动是否为线上进行，用户输入中提到‘通过Zoom在线举行’。2. 这表明活动是线上进行的，因此推理出值为true。3. 参数类型是boolean，无需转换。4. 最终值为true。",
  "参数值": true
}}
```

### 示例 4：失败示例（未找到匹配参数）

**用户历史输入：**
公司将在2024年3月15日于上海举办年度大会，预计有150人参会。
**用户查询：**
创建一个活动
**用户输入：**

```json
{{
  "函数名": "create_event",
  "函数描述": "创建一个活动，包括名称、日期、地点和活动类型。",
  "参数名": "speaker_name",
  "参数描述": "活动主讲人的姓名。",
  "参数类型": "string"
}}
```

**输出 JSON：**

```json
{{
  "推理过程": "1. 需要提取的参数是主讲人姓名，用户输入中没有相关信息。2. 无法推理出值。3. 无需转换。4. 返回null。",
  "参数值": null
}}
```"""
                entities = {}
                task_msg = [{"role": "system", "content": system_prompt}]
                user_input = '\n'.join(questions[:i + 1])


                # 因为当只有一个工具使用时候，“问题改写”容易有幻觉生成虚假参数，所以对只有一个工具的调用去掉问题改写
                if len(tasks)>1:
                    task_msg.append({"role": "assistant", "content": f"**用户历史输入：**：\n{user_input} \n结合用户历史输入查询“{task['问题改写']}”"})
                else:
                    task_msg.append({"role": "assistant", "content": f"**用户历史输入：**：\n{user_input} \n根据用户输入使用提取工具参数。"})


                # task_info = task["任务上下文"]
                task_info = "\n".join(questions[:i + 1])

                for param_name, v in api["parameters"]["properties"].items():
                    user_input_json={
                        "函数名": api_name,
                        "函数描述": api_desc,
                        "参数名": param_name,
                        "参数描述": v["description"],
                        "参数类型": v["type"],   # "输入文本": "\n".join(questions[:index + 1])
                    }
                    if "item" in v:
                        user_input_json["item"]=v["item"]
                    user_input = json.dumps(user_input_json, ensure_ascii=False, indent=4)
                    # print(user_input)
                    temp_msg = deepcopy(task_msg)
                    temp_msg.append({"role": "user", "content": user_input})

                    response = llm_client_2.create_chat_completion(temp_msg, temperature=0.2,
                                                                 skip_cache=retry
                                                                 )
                    # print(response['content'])





                    for _ in range(3):  # Retry up to 3 times
                        try:
                            json_str = re.findall(r"```json(.*)```", response["content"], re.DOTALL)[0]
                        except Exception as e:
                            # 这种错误，通常是因为重复输出，是无法修复的，抛出异常，让问题重新跑一遍。
                            raise e


                        try:
                            content = json.loads(json_str)
                            if content["参数值"] is not None:
                                entities[param_name] = content["参数值"]
                            break  # Exit loop if successful
                        except Exception as e:
                            print(e)
                            fix_json_prompt = f"""以下是一个可能损坏的 JSON 数据以及程序解析 JSON 时的错误消息。请检查 JSON 的结构，并根据错误信息修复任何语法错误（如缺少的逗号、引号、花括号等），确保它是有效的 JSON 格式，并尽量保留原始数据内容。  

**损坏的 JSON：**  
```json
{json_str}  
```

**错误消息：**  
{e}  

请返回修复后的 JSON，不要添加额外的解释或注释，仅返回修复后的 JSON 代码，代码使用```json ```包裹。
                                        """
                            response = llm_client.create_chat_completion([{"role": "user", "content": fix_json_prompt}])
                            json_str = re.findall(r"```json(.*)```", response["content"], re.DOTALL)[0]
                            continue

                function_name = api["name"]
                arguments_dict = entities
                # print("调用函数：", function_name, arguments_dict)
                succeed, task_msg = simulation_tool_use(apis, function_name, arguments_dict)
                print("调用结果", succeed, task_msg)
                sub_task_return += f"使用工具 【{i + 1}】, {task_msg}。"
                if succeed:
                    this_sub_task_targets.append({
                        "name": function_name,
                        "arguments": arguments_dict,
                    })

            user_task_requery_messages.append({"role": "user", "content": sub_task_return})
            targets.append(this_sub_task_targets)
        else:
            user_task_requery_messages.append({"role": "user", "content": "不用使用工具"})
            targets.append([])

    return targets


if __name__ == '__main__':
    import dotenv

    dotenv.load_dotenv()
    llm_client = LLMClient(
        # model=os.getenv("QWEN_MODEL"),
        # base_url=os.getenv("QWEN_BASE_URL"),
        # api_key=os.getenv("QWEN_API_KEY"),
        model=os.getenv("OPENAI_MODEL"),
        base_url=os.getenv("OPENAI_BASE_URL"),
        api_key=os.getenv("OPENAI_API_KEY"),
        # model=os.getenv("QWEN_MODEL"),
        # model=os.getenv("DS_MODEL"),
        # api_key=os.getenv("DS_API_KEY"),
        # base_url=os.getenv("DS_BASE_URL")
    )
    # hello = llm_client.create_chat_completion([{"role": "user", "content": "你好"}],
    #                                           # skip_cache=False,
    #                                           # update_cache=False
    #                                           )
    # print(hello)

    ds_llm = LLMClient(
        # model=os.getenv("QWEN_MODEL"),
        model=os.getenv("DS_MODEL"),
        api_key=os.getenv("DS_API_KEY"),
        base_url=os.getenv("DS_BASE_URL")
    )


    with open("data/合成数据决赛赛题.jsonl", "r", encoding="utf-8") as f:
        test_date = [json.loads(line) for line in f]
    # with open("res/半手工数据集.jsonl", "r", encoding="utf-8") as f:
    #     # 载入jsonl
    #     test_date = json.load(f)
    # with open(r"E:\download\下载\test_qwen.jsonl","r",encoding="utf-8") as f:
    #     qwen_date = [json.loads(line) for line in f]

    print("载入完成: ", len(test_date))

    # for i in range(0,len(test_date)):
        # if test_date[i]["id"] != '23':
        #     continue
    for i in range(0,len(test_date)):
        if i != 13:
            continue
        id = test_date[i]['id']
        # 取出qwen data 对应id数据
        # qwen_data_targets = [qwen_data for qwen_data in qwen_date if qwen_data['id']==id][0]["targets"]
        print(f"正在处理第 {i} 条数据：id:{id}")

        targets=messages_to_function_call(
            llm_client,
            test_date[i]["user_messages"],
            test_date[i]["apis"],
            llm_client_2=ds_llm,
        )
        print("\n".join(test_date[i]["user_messages"]))
        print(targets)

        # print(qwen_data_targets)
        # 比较是否完全一样
        # for j in range(len(targets)):
        #     if targets[j]!=qwen_data_targets[j]:
        #         print("不一样")
        #         print(targets[j])
        #         print(qwen_data_targets[j])
        # name=[api[0]["name"] for api in targets if len(api)>0]
        # if len(name)>0:
        #     name=name[0]
        #     temp=[api for api in test_date[i]["apis"] if api["name"]==name]
        #     if len(temp)==0:
        #         print("没有使用工具")
        #         print(json.dumps(test_date[i]["apis"], ensure_ascii=False,indent=4))
        #     print(json.dumps(temp, ensure_ascii=False, indent=4))